using System;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace WcfExtensions.ServiceModel.Channels
{
    internal class CompressionReplyChannel : CompressionChannel<IReplyChannel>, IReplyChannel
    {
        internal CompressionReplyChannel(ChannelManagerBase channelManagerBase, IReplyChannel innerChannel)
            : base(channelManagerBase, innerChannel)
        {
        }

        #region IReplyChannel Members

        public IAsyncResult BeginReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return InnerChannel.BeginReceiveRequest(timeout, callback, state);
        }

        public IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state)
        {
            return InnerChannel.BeginReceiveRequest(callback, state);
        }

        public IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return InnerChannel.BeginTryReceiveRequest(timeout, callback, state);
        }

        public IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return InnerChannel.BeginWaitForRequest(timeout, callback, state);
        }

        //IIS using async calls with SessionMode.NotAllowed
        public RequestContext EndReceiveRequest(IAsyncResult result)
        {
            return ChangeMessageInContext(InnerChannel.EndReceiveRequest(result));
        }

        public bool EndTryReceiveRequest(IAsyncResult result, out RequestContext context)
        {
            bool innerResult = InnerChannel.EndTryReceiveRequest(result, out context);

            if (innerResult && context != null)
            {
                context = ChangeMessageInContext(context);
            }

            return innerResult;
        }

        public bool EndWaitForRequest(IAsyncResult result)
        {
            return InnerChannel.EndWaitForRequest(result);
        }

        public EndpointAddress LocalAddress
        {
            get { return InnerChannel.LocalAddress; }
        }

        public RequestContext ReceiveRequest(TimeSpan timeout)
        {
            return InnerChannel.ReceiveRequest(timeout);
        }

        public RequestContext ReceiveRequest()
        {
            return InnerChannel.ReceiveRequest();
        }

        public bool TryReceiveRequest(TimeSpan timeout, out RequestContext context)
        {
            return InnerChannel.TryReceiveRequest(timeout, out context);
        }

        public bool WaitForRequest(TimeSpan timeout)
        {
            return InnerChannel.WaitForRequest(timeout);
        }

        #endregion

        private static RequestContext ChangeMessageInContext(RequestContext src)
        {
            return src.RequestMessage == null ? src : new CompressionRequestContext(src);
        }

        #region Nested type: CompressionRequestContext

        private sealed class CompressionRequestContext : RequestContext
        {
            private readonly RequestContext context;
            private readonly Message _decompressedMessage;
            private readonly MessageCompressor _innerCompressor;

            public CompressionRequestContext(RequestContext src)
            {
                context = src;

                // Reflect incoming compressionstate, and configure appropriate compressor.
                CompressionMessageHeader header;
                _decompressedMessage = MessageCompressor.DecompressMessage(src.RequestMessage, out header);
                if (header == null)
                {
                    _innerCompressor = new MessageCompressor(CompressionAlgorithm.GZip, CompressionLevel.NoCompression);
                }
                else
                {
                    _innerCompressor = new MessageCompressor(header.Algorithm, header.Level);
                }
            }

            public override Message RequestMessage
            {
                get { return _decompressedMessage; }
            }

            private MessageCompressor InnerCompressor
            {
                get { return _innerCompressor; }
            }

            public override void Abort()
            {
                context.Abort();
            }

            public override void Close()
            {
                context.Close();
            }

            public override void Close(TimeSpan timeout)
            {
                context.Close(timeout);
            }

            public override IAsyncResult BeginReply(Message message, AsyncCallback callback, object state)
            {
                return context.BeginReply(message == null ? message : InnerCompressor.CompressMessage(message), callback, state);
            }

            public override IAsyncResult BeginReply(Message message, TimeSpan timeout, AsyncCallback callback,
                                                    object state)
            {
                return context.BeginReply(message == null ? message : InnerCompressor.CompressMessage(message), timeout, callback, state);
            }

            public override void EndReply(IAsyncResult result)
            {
                context.EndReply(result);
            }

            //This is the main reply point, we compress here.
            public override void Reply(Message message)
            {
                context.Reply(message == null ? message : InnerCompressor.CompressMessage(message));
            }

            //IIS uses this one!
            public override void Reply(Message message, TimeSpan timeout)
            {
                context.Reply(message == null ? message : InnerCompressor.CompressMessage(message), timeout);
            }
        }

        #endregion
    }
}